package clm.logmerge.nio;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Iterator;
import java.util.Scanner;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class LogFileMerge {
	
    private static final String DEFAULT_DATE_PATTERN = "\\[\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}.\\d{3}\\]";
    private static final String DEFAULT_DATE_FORMAT = "[yyyy-MM-dd HH:mm:ss.SSS]";
    private static String datePattern = DEFAULT_DATE_PATTERN;
    private static String dateFormat = DEFAULT_DATE_FORMAT;
	private static Pattern DATE_PATTERN = Pattern.compile("^\\s*(" + datePattern + ").*$");
	private static SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
    
	
	static long diskTime = 0;
	static long diskStart;
	static Runtime r = Runtime.getRuntime();
	

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		long start = System.currentTimeMillis();
        int argAdjustor = 0;
		
		

		//check arguments
		if(args.length < 3){
            printUsage();
            return;
		}
		
        if(args[1].equalsIgnoreCase("date")){
            if(args.length < 6){
                printUsage();
                return;
            } else {
                System.out.println("setting date");
                dateFormat = args[2];
                datePattern = args[3];

                System.out.println("dateFormat = " + dateFormat);
                System.out.println("datePattern = " + datePattern);

                argAdjustor = argAdjustor + 3;
                
                DATE_PATTERN = Pattern.compile("^\\s*(" + datePattern + ").*$");
                sdf = new SimpleDateFormat(dateFormat);
                
            }
        }

 
		
		System.out.println("Processing...");
		
		TreeSet<LineDTO> lineData = readSetup(args, argAdjustor);
		r.gc();

		//printTree(lineData);
		//r.gc();
		System.out.println("Merging...");
		int linesProcessed = 0;
	
		linesProcessed = writeTreeQuick(lineData, args, argAdjustor);
		
		//r.gc();
		long end = System.currentTimeMillis();
		long duration = end - start;
		System.out.println("Process completed in:\t" + (duration) + "ms (" + (new BigDecimal((duration) / 1000.0 / 60).setScale(2, BigDecimal.ROUND_HALF_EVEN)) + " minutes)");
		System.out.println("Disk time:\t\t" + (diskTime) + "ms (" + (new BigDecimal((diskTime) / 1000.0 / 60).setScale(2, BigDecimal.ROUND_HALF_EVEN)) + " minutes)");
		System.out.println("Entries processed:\t" + linesProcessed + "(" + (new BigDecimal(linesProcessed / (duration / 1000.0)).setScale(2, BigDecimal.ROUND_HALF_EVEN)) + " entries/second)");
		
	}
	
    public static void printUsage() {
        System.out.println("Usage (list files individually):");
        System.out.println("\tjava -jar logMergeSlim.jar output.txt [date <date format> <date expresion>] input1.txt input2.txt input3.txt");
        System.out.println("\tAll paths should be the full file path (ex. C:\\Temp\\merge_output.txt)");
        System.out.println("\targ[0]:\t\toutput destination");
        System.out.println("\targ[1]-arg[n]:\tinput files to merge");
        System.out.println();
        System.out.println("Usage (scan directory):");
        System.out.println("\tjava -jar logMergeSlim.jar output.txt [date <date format> <date expresion>] scan c:\\logs");
        System.out.println("\tAll paths should be the full file path (ex. C:\\Temp\\merge_output.txt)");
        System.out.println("\targ[0]:\t\toutput destination");
        System.out.println("\targ[1]:\t\tscan");
        System.out.println("\targ[2]:\t\tdirectory to scan");
        System.out.println();
        System.out.println();
        System.out.println("\tDefault Date Format is '" + DEFAULT_DATE_FORMAT + "'");
        System.out.println("\tDefault Date Pattern is '" + DEFAULT_DATE_PATTERN + "'");

    }
	
	private static TreeSet<LineDTO> readSetup(String[] args, int argAdjustor ){
		TreeSet<LineDTO> lineData = new TreeSet<LineDTO>(new LineComparator<LineDTO>());
		try {
			if(args[1 + argAdjustor].equalsIgnoreCase("scan")){
				File directory = new File(args[2 + argAdjustor]);
				File[] fileArray = directory.listFiles();
				int fileCount = 0;
				for(int i = 0; i < fileArray.length; i++){
					if(fileArray[i].isFile()){
						System.out.println("Reading file: " + fileArray[i] + "...");
						Scanner s = new Scanner(fileArray[i]);
						lineData.addAll(readFiles(s, fileCount+1));
						s.close();
						fileCount++;
						r.gc();
					}
				}
			}
			else{
				for(int i = 1 + argAdjustor; i < args.length; i++){
					System.out.println("Reading file: " + args[i] + "...");
					Scanner s = new Scanner(new File(args[i]));
					lineData.addAll(readFiles(s, i));
					s.close();
					r.gc();
				}
			}
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return lineData;
	}
	
	private static TreeSet<LineDTO> readFiles(Scanner scanner, int fileReference){
		TreeSet<LineDTO> lineData = new TreeSet<LineDTO>(new LineComparator<LineDTO>());

		//the "\n" delimiter will work for both "\r\n" and "\n" new lines, but not for lines
		//terminated by only "\r"
		scanner.useDelimiter("\n");
		
		String dateString;
		LineDTO lineDTO = null;
		boolean isFirstLine = true;
		int readOrder = 0;
		long beginPointer = 0;
		long lastPointer = 0;
		
		try {
			String str;
			diskStart = System.currentTimeMillis();
			str = scanner.next();
			diskTime += (System.currentTimeMillis() - diskStart);
			while (str != null) {
				
				Matcher matcher = DATE_PATTERN.matcher(str);
				if(matcher.find()){
					//if first time through, skip this
					if(!isFirstLine){
						//we found the next entry, so finish processing the current entry
						lineDTO.setLineBytes(lastPointer - beginPointer);
						lineDTO.setReadOrder(readOrder);
						lineDTO.setFileReference(fileReference);
						lineData.add(lineDTO);
					}
					else{
						isFirstLine = false;
					}
					//process current line
					readOrder++;
					lineDTO = new LineDTO();
					beginPointer = lastPointer;
					lineDTO.setLinePointer(beginPointer);
					dateString = matcher.group(1);
					lineDTO.setLineDate(sdf.parse(dateString));
				}
				diskStart = System.currentTimeMillis();
				lastPointer += str.getBytes().length + 1;
				if(scanner.hasNext())
					str = scanner.next();
				else
					str = null;
				diskTime += (System.currentTimeMillis() - diskStart);
			}
			if(lineDTO != null){
				//finish up processing last line(s)
				lineDTO.setLineBytes(lastPointer - beginPointer);
				lineDTO.setReadOrder(readOrder);
				lineDTO.setFileReference(fileReference);
				lineData.add(lineDTO);
			}
		} catch (ParseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return lineData;
	}

	
	
	private static int writeTreeQuick(TreeSet<LineDTO> ts, String[] args, int argAdjustor) throws OutOfMemoryError{
		int iteration = 0;
		int lineRecords = ts.size();
		try {
			
			BufferedWriter bw = new BufferedWriter(new FileWriter(new File(args[0])));
			FileChannel[] fcArray = null;
			String str;
			StringBuffer strBuf = new StringBuffer();
			
			if(args[1 + argAdjustor].equalsIgnoreCase("scan")){
				File directory = new File(args[2 + argAdjustor]);
				File[] fileArray = directory.listFiles();
				
				int fileCount = 0;
				for(int i = 0; i < fileArray.length; i++){
					if(fileArray[i].isFile()){
						fileCount++;
					}
				}
				fcArray = new FileChannel[fileCount];
				fileCount = 0;
				for(int i = 0; i < fileArray.length; i++){
					if(fileArray[i].isFile()){
						FileInputStream fis = new FileInputStream(fileArray[i]);
						fcArray[fileCount] = fis.getChannel();
						fileCount++;
						diskStart = System.currentTimeMillis();
						bw.write(fileCount + " - " + fileArray[i].getName() + "\n");
						diskTime += (System.currentTimeMillis() - diskStart);
					}
				}
				diskStart = System.currentTimeMillis();
				bw.write("\n");
				diskTime += (System.currentTimeMillis() - diskStart);
			}
			else{
				fcArray = new FileChannel[args.length-1];
				for(int i = 1 + argAdjustor; i < args.length; i++){
					File file = new File(args[i]);
					FileInputStream fis = new FileInputStream(file);
					fcArray[i-1] = fis.getChannel();
					diskStart = System.currentTimeMillis();
					bw.write(i + " - " + file.getName() + "\n");
					diskTime += (System.currentTimeMillis() - diskStart);
				}
				diskStart = System.currentTimeMillis();
				bw.write("\n");
				diskTime += (System.currentTimeMillis() - diskStart);
			}
			
			ByteBuffer bb = null;
			System.out.println("0%.....................50%....................100%");
			int prevProgress = 0;
			Iterator<LineDTO> iterator = ts.iterator();
			while(iterator.hasNext()){
				LineDTO dto = iterator.next();
				//System.out.println(dto.getLineBytes());
				float percentComplete = 100f * iteration / lineRecords;
				int progress = Math.round(percentComplete) / 2;
				while(progress > prevProgress){
					System.out.print(".");
					prevProgress++;
				}
				diskStart = System.currentTimeMillis();
				fcArray[dto.getFileReference()-1].position(dto.getLinePointer());
				bb = ByteBuffer.allocate(Integer.parseInt(Long.toString(dto.getLineBytes())));
				fcArray[dto.getFileReference()-1].read(bb);
				str = new String(bb.array());
				diskTime += (System.currentTimeMillis() - diskStart);
				strBuf = new StringBuffer();
				//strBuf.append("\n");
				strBuf.append(dto.getFileReference());
				strBuf.append(" ");
				strBuf.append(str);
				
				diskStart = System.currentTimeMillis();
				bw.write(strBuf.toString());
				diskTime += (System.currentTimeMillis() - diskStart);
				
				iteration++;
			}
			System.out.println();
			diskStart = System.currentTimeMillis();
			bw.close();
			for(int i = 0; i < fcArray.length; i++){
				fcArray[i].close();
			}
			diskTime += (System.currentTimeMillis() - diskStart);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (OutOfMemoryError e) {
			throw e;
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return iteration;
	}
	
	


}
